package com.richly.iot;

import cn.hutool.core.util.StrUtil;
import com.richly.enums.IotEnum;
import com.richly.annotation.IotTableName;
import com.richly.dto.TestDataDto;
import com.richly.dto.base.Record;
import com.richly.dto.base.Recordable;
import lombok.extern.slf4j.Slf4j;
import org.apache.iotdb.session.pool.SessionDataSetWrapper;
import org.apache.iotdb.session.pool.SessionPool;
import org.apache.iotdb.tsfile.file.metadata.enums.CompressionType;
import org.apache.iotdb.tsfile.file.metadata.enums.TSDataType;
import org.apache.iotdb.tsfile.file.metadata.enums.TSEncoding;
import org.apache.iotdb.tsfile.read.common.Field;
import org.apache.iotdb.tsfile.read.common.RowRecord;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import javax.annotation.PostConstruct;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * @Author: l_y
 * @Date: 2022/6/10 13:23
 * @FileName: IotDbUtils
 * @Description: iotDb
 */
@Component
@Slf4j
public class IotDbUtils {

    private static final String STORAGE_GROUP_ID = IotEnum.ROOT.getPathPrefix()
            + IotEnum.SPACE.getPathPrefix() + IotEnum.TEST.getPathPrefix();
    private static final String SPACE = IotEnum.SPACE.getPathPrefix();

    private static final List<Class<? extends Recordable>> classRecords = new ArrayList<>();

    @Autowired
    private SessionPool session;
    @Autowired
    private ApplicationArguments arguments;

    /**
     * 第一次运行时候 需要在启动参数中 加first 标注 触发建表 储存组
     */
    @PostConstruct
    public void initAll() {
        List<String> run = arguments.getNonOptionArgs();
        if (CollectionUtils.isEmpty(run)) {
            return;
        }
        if (StrUtil.equals(run.get(0), "first")) {
            initIotDb();
        }
    }

    static {
        classRecords.add(TestDataDto.class);
    }

    public void initIotDb() {
        try {
            session.setStorageGroup(STORAGE_GROUP_ID);

            classRecords.forEach(recordable -> {
                IotTableName iotTableName = recordable.getAnnotation(IotTableName.class);
                String tableName = iotTableName.value();
                java.lang.reflect.Field[] fields = recordable.getDeclaredFields();
                for (java.lang.reflect.Field field : fields) {
                    field.setAccessible(true);
                    try {
                        session.createTimeseries(
                                STORAGE_GROUP_ID + SPACE + tableName + SPACE + tableName + SPACE + field.getName(),
                                convertTypeByEntity(field.getType().getName()),
                                convertEncodingByEntity(field.getType().getName()), CompressionType.LZ4);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }

            });
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private List<String> getDeviceIds(List<? extends Recordable> records) {
        List<String> deviceIds = new ArrayList<String>();
        for (Recordable recordable : records) {
            Record record = recordable.toRecord();
            String deviceId = record.getDeviceId();
            deviceIds.add(deviceId);
        }
        return deviceIds;
    }

    private List<Long> getTimes(List<? extends Recordable> records) {
        List<Long> times = new ArrayList<Long>();
        for (Recordable recordable : records) {
            Record record = recordable.toRecord();
            times.add(record.getTime());
        }
        return times;
    }

    private List<List<String>> getMeasurementsList(List<? extends Recordable> records) {
        List<List<String>> measurementsList = new ArrayList<List<String>>();
        for (Recordable recordable : records) {
            Record record = recordable.toRecord();
            measurementsList.add(record.getMeasurements());
        }
        return measurementsList;
    }

    private List<List<String>> getValuesList(List<? extends Recordable> records) {
        List<List<String>> valuesList = new ArrayList<List<String>>();
        for (Recordable recordable : records) {
            Record record = recordable.toRecord();
            valuesList.add(record.getValues());
        }

        return valuesList;
    }

    private TSDataType convertTypeByEntity(String type) {
        switch (type) {
            case "java.lang.String":
                return TSDataType.TEXT;
            case "java.util.Date":
                return TSDataType.TEXT;
            case "java.math.BigDecimal":
                return TSDataType.DOUBLE;
            case "java.lang.Integer":
                return TSDataType.INT64;
            default:
                return TSDataType.TEXT;
        }
    }

    private TSEncoding convertEncodingByEntity(String type) {
        switch (type) {
            case "java.lang.String":
                return TSEncoding.PLAIN;
            case "java.util.Date":
                return TSEncoding.PLAIN;
            case "java.math.BigDecimal":
                return TSEncoding.RLE;
            case "java.lang.Integer":
                return TSEncoding.RLE;
            default:
                return TSEncoding.PLAIN;
        }
    }

    public void insertDataToIot(List<? extends Recordable> records) {
        try {
            session.insertRecords(getDeviceIds(records), getTimes(records), getMeasurementsList(records),
                    getValuesList(records));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 时间查询时 time属性 时间格式20220101000000的字符串时间可查
     *
     * @param t       t
     * @param wrapper wrapper
     */
    public List<Recordable> queryDataToIot(Class<? extends Recordable> t, IotWrapper wrapper) {
        SessionDataSetWrapper sessionDataSet = null;
        List<Recordable> records = new ArrayList<>();
        try {
            IotTableName annotation = t.getAnnotation(IotTableName.class);
            String tableName = annotation.value();
            String sql =
                    IotWrapperAnalysis.analysisToSql(wrapper, STORAGE_GROUP_ID + SPACE + tableName + SPACE + tableName);
            sessionDataSet = session.executeQueryStatement(sql);
            int fetchSize = sessionDataSet.getBatchSize();
            List<String> columnNames = sessionDataSet.getColumnNames();
            List<String> columnTypes = sessionDataSet.getColumnTypes();
            if (fetchSize > 0) {
                while (sessionDataSet.hasNext()) {
                    Recordable recordable = t.newInstance();
                    RowRecord next = sessionDataSet.next();
                    List<Field> fields = next.getFields();
                    for (int i = 0; i < fields.size(); i++) {
                        String columName = columnNames.get(i + 1);
                        Field field = fields.get(i);

                        String colum = columName.substring(columName.lastIndexOf(".") + 1);
                        String fileName = colum.substring(0, 1).toUpperCase() + colum.substring(1);
                        if (StrUtil.equals(columnTypes.get(i + 1), TSDataType.TEXT.toString())
                                && !StrUtil.containsAnyIgnoreCase(columName, "date", "time")) {
                            Method method = t.getMethod("set" + fileName, String.class);
                            method.invoke(recordable, field.getStringValue());
                        }
                        if (StrUtil.equals(columnTypes.get(i + 1), TSDataType.TEXT.toString())
                                && StrUtil.containsAnyIgnoreCase(columName, "date", "time")) {
                            Method method = t.getMethod("set" + fileName, Date.class);
                            SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                            String value = field.getStringValue();
                            method.invoke(recordable, format.parse(value));
                        }
                        if (StrUtil.equals(columnTypes.get(i + 1), TSDataType.FLOAT.toString())) {
                            Method method = t.getMethod("set" + fileName, BigDecimal.class);
                            method.invoke(recordable, BigDecimal.valueOf(field.getFloatV()));
                        }
                        if (StrUtil.equals(columnTypes.get(i + 1), TSDataType.DOUBLE.toString())) {
                            Method method = t.getMethod("set" + fileName, BigDecimal.class);
                            method.invoke(recordable, BigDecimal.valueOf(field.getDoubleV()));
                        }
                    }
                    records.add(recordable);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            sessionDataSet.close();
        }
        return records;
    }

}
